#include "table-structures.h"

void InitTableStructures() {
}

bool IsTriggerObject(Thing o) {
  if (!o.IsArray()) return false;
  Box * b = o.GetBox();
  if (b->ArraySize() != 3) return false;
  return IsTriggerSetName(b->Ref(0));
}

// (triggers <x>)
bool IsTriggerSetName(Thing o) {
  if (!o.IsArray()) return false;
  Box * b = o.GetBox();
  if (b->ArraySize() != 2) return false;
  if (b->Ref(0) != TRIGGERS) return false;
  return true;
}

int64 SetModel::PartialDL(Thing set_name, Thing reader, Thing element) {
  if (IsTriggerObject(reader)) return 0;
  if (reader == TRIGGERS) return 0;
  return ModelReadPartialDL(set_name, reader, element);
}

int64 SetModel::ModelReadPartialDL
(Thing set_name, Thing reader, Thing element) {
  TableSet input = InputSet(set_name);
  TableSet output = OutputSet(set_name, reader);
  int64 in_count = input.Count(element);
  int64 out_count = output.Count(element);
  int64 in_total = input.weight();
  int64 out_total = output.weight();
  return SelfModelingPartialDL(in_count, out_count, in_total, out_total);
}

// Designators produce integer values.  
// (these values govern how many times triggers fire for each input element)
// call this when a designator starts or stops producing a value.
BIND_HASH(kValueType);
void SetModel::UpdateDesignatorRead
(Thing designator, int64 value, int64 delta) {
  // cout << "::UpdateDesignatorRead" << endl;
  // DEBUG(designator); DEBUG(value); DEBUG(delta); 
  if (delta == 0) return;
  uint64 partial_key = Fingerprint(designator.ID(), kValueType);
  int64 old_total = 0;
  int64 value_type = min(value, 2ll);
  if (value_type == 2) {
    UpdateDL(delta * IntegerDL(value-2));
    // save a factor of value! because we could have represented the executions
    // of the calling trigger in any order. 
    UpdateDL(-delta * MLnF(value));
  }
  for (int64 i=0; i<3; i++) {
    uint64 key = Fingerprint(partial_key, i);
    int64 old_count = TableIntegerGet(table_, key);
    old_total += old_count;
    if (i == value_type) {
      TableIntegerAdd(table_, key, delta);
      int64 new_count = delta + old_count;
      UpdateDL(-(MLnF(new_count) - MLnF(old_count)));
    }
  }
  int64 new_total = old_total + delta;
  UpdateDL(MLnF(new_total + 2) - MLnF(old_total + 2));
}

void SetModel::AddInputNoMagic
(Thing set_name, Thing element, int64 delta) {
  // cout << "::AddInputNoMagic" << endl;
  // DEBUG(set_name); DEBUG(element); DEBUG(delta);

  CHECK(delta != 0);

  // Loop over all readers and update things
  TableSet input = InputSet(set_name);
  TableSet readers = ReadersSet(set_name);
  int64 readers_count = readers.size();

  int64 dl = 0;
  for (int64 i=0; i<readers_count; i++) {
    Thing reader = readers.nth(i);
    dl -= PartialDL(set_name, reader, element);    
  }
  input.Update(element, delta);
  int64 set_exists_delta = 
    (input.new_size_ ? 1 : 0) - (input.old_size_ ? 1 : 0);
  if (set_exists_delta) AllSetsSet().Update(set_name, set_exists_delta);
  SetsSet(element).Update(set_name, delta);
  for (int64 i=0; i<readers_count; i++) {
    Thing reader = readers.nth(i);
    dl += PartialDL(set_name, reader, element);
  }
  UpdateDL(dl);

  int64 element_exists_delta =
    (input.new_count_ ? 1 : 0) - (input.old_count_ ? 1 : 0);
  // DEBUG(input.new_count_); DEBUG(input.old_count_);
  if (!element_exists_delta) return;
  // cout << "past element exists delta" << endl;

  // Some things need to happen once new values exist for a set

  // When adding an element, all trigger reader designators produce an extra 0
  TableSet t_inputs = InputSet(TriggersModelSet(set_name));
  int64 t_count = t_inputs.size();
  for (int64 i=0; i<t_count; i++) {
    Thing trigger_time = t_inputs.nth(i);
    Thing trigger = trigger_time.Ref(0);
    // DEBUG(trigger);
    Thing d = TriggerToDesignator(trigger);
    //cout << "d:" << d << endl;
    //cout << "hardcoded 0" << endl;
    UpdateDesignatorRead(d, 0, element_exists_delta);
  }

  if (!IsTriggerSetName(set_name)) return;

  // When adding a trigger:
  // set_name is (triggers <base_set>)
  // element is (((triggers <base_set>) <designator> <code>) <time>)
  // - add a new reader to the base set
  // - add n zeroes to the designator where n = the size of the base set
  // DEBUG(element);
  Thing trigger = element.Ref(0);
  CHECK(IsTriggerObject(trigger));
  CHECK(trigger.Ref(0) == set_name);
  Thing base_set = set_name.Ref(1);
  TableSet base_set_inputs = InputSet(base_set);
  TableSet base_set_readers = ReadersSet(base_set);
  base_set_readers.Update(trigger, element_exists_delta);
  Thing designator = TriggerToDesignator(trigger);
  //cout << "designator:" << designator << endl;
  //cout << "hardcoded 0 (2)" << endl;
  UpdateDesignatorRead
    (designator, 0, element_exists_delta * base_set_inputs.size());
}
 
void SetModel::AddOutputNoMagic
(Thing set_name, Thing reader, Thing element, int64 delta) {
  CHECK(delta != 0);
  // cout << "::AddOutputNoMagic" << endl;
  // DEBUG(set_name); DEBUG(reader); DEBUG(element); DEBUG(delta);
  TableSet input = InputSet(set_name);
  TableSet readers = ReadersSet(set_name);
  readers.Update(reader, delta);
  TableSet output_set = OutputSet(set_name, reader);
  int64 dl = -PartialDL(set_name, reader, element);
  output_set.Update(element, delta);
  dl += PartialDL(set_name, reader, element);
  UpdateDL(dl);

  if (IsTriggerObject(reader)) {
    Thing designator = reader.Ref(1);
    // DEBUG(output_set.old_count_);
    UpdateDesignatorRead(designator, output_set.old_count_, -1);
    // DEBUG(output_set.new_count_);
    UpdateDesignatorRead(designator, output_set.new_count_, 1);
  }
}

int64 SetModel::OutputCount(Thing set_name, Thing element) {
  int64 ret = 0;
  TableSet readers = ReadersSet(set_name);
  for (uint64 i=0; i<readers.size(); i++) 
    ret += OutputSet(set_name, readers.nth(i)).Count(element);
  return ret;
}

int64 SetModel::InputCount(Thing set_name, Thing element) {
  return InputSet(set_name).Count(element);
}

// element is time-value
void SetModel::AddMagicPost(Thing set_name, Thing element, int64 delta) {
  // cout << "::AddMagicPost" << endl;
  // DEBUG(set_name); DEBUG(element); DEBUG(delta);
  TableSet magic = MagicSet(set_name);
  int64 old_set_exists = magic.size() ? 1 : 0;
  int64 old_size = magic.size();
  UpdateDL(MLnF(old_size));
  if (old_size) UpdateDL(-MLn(old_size));
  magic.Update(element, delta);
  int64 new_set_exists = magic.size() ? 1 : 0;
  int64 new_size = magic.size();
  UpdateDL(-MLnF(new_size));
  if (new_size) UpdateDL(MLn(new_size));
  gdl_.Add(set_name, (new_set_exists - old_set_exists));
  gdl_.Add(element, delta);
  AddInputNoMagic(set_name, element, delta);
}

bool SetModel::MagicPostExists(Thing set_name, Thing element) {
  return MagicSet(set_name).Count(element);
}

int64 SetModel::NumberMagicPosts(Thing set_name) {
  return MagicSet(set_name).size();
}

// assumes a positive delta
void SetModel::AboutToAddOutput(Thing set_name, Thing element) {
  //cout << "::AboutToAddOutput" << endl;
  //DEBUG(set_name); DEBUG(element);
  TableSet input = InputSet(set_name);
  int64 count = input.Count(element);
  if (count > 0) return;
  AddMagicPost(set_name, element, 1);
}

void SetModel::JustRemovedOutput(Thing set_name, Thing element) {
  //cout << "::JustRemovedOutput" << endl;
  //DEBUG(set_name); DEBUG(element);
  if (!MagicPostExists(set_name, element)) return;
  if (OutputCount(set_name, element)) return;
  AddMagicPost(set_name, element, -1);
}

void SetModel::AboutToRemoveInput
(Thing set_name, Thing element, int64 delta) {
  int64 in_count = InputCount(set_name, element);
  CHECK((in_count + delta) >= 0);
  if (in_count + delta) return;
  if (!OutputCount(set_name, element)) return;
  AddMagicPost(set_name, element, 1);
}

void SetModel::JustAddedInput
(Thing set_name, Thing element) {
  if (MagicPostExists(set_name, element))
    AddMagicPost(set_name, element, -1);
}

// delta can be negative
void SetModel::AddOutput
(Thing set_name, 
 Thing reader,
 Thing element, 
 int64 delta) {
  // cout << "::AddOutput" << endl;
  // DEBUG(set_name); DEBUG(reader); DEBUG(element); DEBUG(delta);
  if (delta == 0) return;
  if (delta > 0) AboutToAddOutput(set_name, element);
  AddOutputNoMagic(set_name, reader, element, delta);
  if (delta < 0) JustRemovedOutput(set_name, element);
}

void SetModel::AddInput
(Thing set_name,
 Thing element,
 int64 delta) {
  //cout << "::AddInput" << endl;
  //DEBUG(set_name); DEBUG(element); DEBUG(delta);
  if (delta == 0) return;
  if (delta < 0) AboutToRemoveInput(set_name, element, delta);
  AddInputNoMagic(set_name, element, delta);
  if (delta > 0) JustAddedInput(set_name, element);
}

/*
void TestSetModel() {
  InitAllocator();
  InitSet();
  Table<uint64> t(1, false, NULL);
  SetModel m(&t);
  string command;
  Thing set_name;
  Thing element;
  ThreadAllocator a;
  while (cin >> command) {
    set_name = ReadThing(&a, cin);
    element = ReadThing(&a, cin);
    if (command == "ai") m.Add(true, set_name, element, 1, &a);
    if (command == "di") m.Add(true, set_name, element, -1, &a);
    if (command == "ao") m.Add(false, set_name, element, 1, &a);
    if (command == "do") m.Add(false, set_name, element, -1, &a);
    cout << "DL = " << m.UpdateDL(0) << endl;
    cout << m.ToString() << endl;
  }
}
*/

void explore(TableSet & h) {
  string cmd;
  uint64 k;
  int64 delta;
  while (1) {
    cin >> k >> delta;
    if (k == 999) return;
    h.Update(k, delta);
    
    cout << h.ToString() << endl;
  }
}

int TableSetTest() {
  Table<Thing> t(1, false, NULL);
  TableSet s(&t, 8876);
  explore(s);
  return 0; 
}

